﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Microsoft.Build.Framework;
using System.Diagnostics;
using System.Xml.XPath;
using System.Xml;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using EnvDTE;
using Process = System.Diagnostics.Process;
using ServiceProvider = Microsoft.VisualStudio.Shell.ServiceProvider;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.ExtensionManager;
using Microsoft.VisualStudio.Shell;

namespace BuildTasks
{
    public class RegisterPackages : ITask
    {
        private string installerPath;
        private IVsExtensionManager extensionManager;
        public IBuildEngine BuildEngine { get; set; }
        public ITaskHost HostObject { get; set; }

        public void RegisterPackage(string packageIdentifier, string packagePath)
        {
            var fileInfo = new FileInfo(packagePath);
            var outdated = false;
            var installedExtension = (IInstalledExtension)null;

            Debug.Assert(fileInfo.Exists);

            Action<DateTime, DirectoryInfo> checkOutdated = null;

            checkOutdated = new Action<DateTime,DirectoryInfo>((date, directory) =>
            {
                foreach (var file in directory.GetFiles())
                {
                    if (file.Extension != ".vsix" && file.Extension != ".zip")
                    {
                        if (DateTime.Compare(file.LastWriteTime, date) > 0)
                        {
                            outdated = true;
                            return;
                        }
                    }
                }

                foreach (var dir in directory.GetDirectories())
                {
                    checkOutdated(date, dir);
                }
            });

            if (extensionManager.TryGetInstalledExtension(packageIdentifier, out installedExtension))
            {
                checkOutdated(installedExtension.InstalledOn.DateTime, fileInfo.Directory);

                if (!outdated)
                {
                    return;
                }
            };

            var process = new Process();
            var startInfo = new ProcessStartInfo
            {
                UseShellExecute = false,
                RedirectStandardError = true,
                CreateNoWindow = true,
                FileName = installerPath,
                Arguments = "/q /u:" + packageIdentifier
            };

            process.StartInfo = startInfo;
            process.Start();

            var result = process.StandardError.ReadToEnd();

            process.WaitForExit();

            if (process.ExitCode != 0)
            {
                var message = new BuildErrorEventArgs(string.Empty, string.Empty, string.Empty, 0, 0, 0, 0, "Error unregistering package.  " + result + " Exit code = " + process.ExitCode.ToString() + ".  You can run Visual Studio as Administrator to install this component.", "", "", DateTime.Now);
                this.BuildEngine.LogErrorEvent(message);
            }
            else
            {
                var message = new BuildMessageEventArgs("Package unregistered successfully.  " + result, "", "", MessageImportance.High);
                this.BuildEngine.LogMessageEvent(message);
            }

            process = new Process();
            startInfo = new ProcessStartInfo
            {
                UseShellExecute = false,
                RedirectStandardError = true,
                CreateNoWindow = true,
                FileName = installerPath,
                Arguments = "/q \"" + packagePath + "\""
            };

            process.StartInfo = startInfo;
            process.Start();

            result = process.StandardError.ReadToEnd();

            process.WaitForExit();

            if (process.ExitCode != 0)
            {
                var message = new BuildErrorEventArgs(string.Empty, string.Empty, string.Empty, 0, 0, 0, 0, "Error registering package.  " + result + " Exit code = " + process.ExitCode.ToString() + ".  You can run Visual Studio as Administrator to install this component.", "", "", DateTime.Now);
                this.BuildEngine.LogErrorEvent(message);

                return;
            }
            else
            {
                var message = new BuildMessageEventArgs("Package registered successfully.  " + result, "", "", MessageImportance.High);
                this.BuildEngine.LogMessageEvent(message);
            }

            // see if there are templates, the VSIX installer does not install them properly due to our zip format not exactly matching Windows.

            fileInfo = new FileInfo(Path.Combine(fileInfo.DirectoryName, "extension.vsixmanifest"));

            if (fileInfo.Exists)
            {
                var stream = File.OpenRead(fileInfo.FullName);
                var installLocation = Path.Combine(Environment.ExpandEnvironmentVariables("%LocalAppData%"), @"Microsoft\VisualStudio\10.0\Extensions");
                var _namespace = "vs";

                stream.Seek(0, SeekOrigin.Begin);

                var xPathDocument = new XPathDocument(stream);

                stream.Close();

                var nsmgr = new XmlNamespaceManager(new NameTable());
                nsmgr.AddNamespace(_namespace, "http://schemas.microsoft.com/developer/vsx-schema/2010");

                var navigator = xPathDocument.CreateNavigator();

                var iter = navigator.Select(string.Format("{0}:Vsix/{0}:Identifier/{0}:Author", _namespace), nsmgr);
                iter.MoveNext();

                var author = iter.Current.Value;

                iter = navigator.Select(string.Format("{0}:Vsix/{0}:Identifier/{0}:Name", _namespace), nsmgr);
                iter.MoveNext();

                var name = iter.Current.Value;

                iter = navigator.Select(string.Format("{0}:Vsix/{0}:Identifier/{0}:Version", _namespace), nsmgr);
                iter.MoveNext();

                var version = iter.Current.Value;

                installLocation = Path.Combine(installLocation, string.Format(@"{0}\{1}\{2}", author, name, version));

                var dirInstall = new DirectoryInfo(installLocation);

                Debug.Assert(dirInstall.Exists);

                Action<DirectoryInfo, DirectoryInfo> copyFilesFolders = null;

                copyFilesFolders = new Action<DirectoryInfo,DirectoryInfo>((source, dest) =>
                {
                    foreach (var file in source.GetFiles())
                    {
                        file.CopyTo(Path.Combine(dest.FullName, file.Name));
                    }

                    foreach (var dir in source.GetDirectories())
                    {
                        var subDir = dest.CreateSubdirectory(dir.Name);

                        copyFilesFolders(dir, subDir);
                    }
                });

                var dirProjectTemplates = new DirectoryInfo(Path.Combine(fileInfo.DirectoryName, "ProjectTemplates"));

                if (dirProjectTemplates.Exists)
                {
                    var subDir = dirInstall.CreateSubdirectory("ProjectTemplates");

                    copyFilesFolders(dirProjectTemplates, subDir);
                }

                var dirItemTemplates = new DirectoryInfo(Path.Combine(fileInfo.DirectoryName, "ItemTemplates"));

                if (dirItemTemplates.Exists)
                {
                    var subDir = dirInstall.CreateSubdirectory("ItemTemplates");

                    copyFilesFolders(dirItemTemplates, subDir);
                }
            }
        }

        public bool Execute()
        {
            try
            {
                var guidService = typeof(SVsExtensionManager).GUID;

                extensionManager = (IVsExtensionManager)ServiceProvider.GlobalProvider.GetService(guidService);

                var packages = new Dictionary<string, string>()
                {
                    {"HydraPackage.e4f2acc8-08f0-4b27-8421-e2b85a04956b", @"CodeGenerationPipeline\VSIX\HydraPackageTemplate\HydraPackageTemplate.vsix"}
                };

                if (Environment.Is64BitOperatingSystem)
                {
                    installerPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), @"Microsoft Visual Studio 10.0\Common7\IDE\VSIXInstaller.exe");
                }
                else
                {
                    installerPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Microsoft Visual Studio 10.0\Common7\IDE\VSIXInstaller.exe");
                }

                var fileInfo = new FileInfo(installerPath);

                Debug.Assert(fileInfo.Exists);

                foreach (var package in packages)
                {
                    var projectFile = new FileInfo(this.BuildEngine.ProjectFileOfTaskNode);
                    var packagePath = Path.Combine(projectFile.Directory.Parent.FullName, package.Value);

                    RegisterPackage(package.Key, packagePath);
                }
            }
            catch (Exception ex)
            {
                var error = string.Format("Error registering packages. You can run Visual Studio as Administrator to install this component.", ex);

                var message = new BuildErrorEventArgs(string.Empty, string.Empty, string.Empty, 0, 0, 0, 0, error, "", "", DateTime.Now);
                this.BuildEngine.LogErrorEvent(message);

                Console.WriteLine(error);
                return false;
            }

            return true;
        }
    }
}
